﻿/*
 * This file is part of MonoSettlers.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://opensettlers.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using OpenTK;

namespace MonoSettlers
{
    /// <summary>
    /// Interaction logic for AnimationTab.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public object Library { get { return TLibrary; } }
        public object CurrentClass { get { return TCurrentClass; } }
        public object CurrentSet { get { return TCurrentSet; } }
        public object CurrentAnim { get { return TCurrentAnim; } }
        public object AnimClass { get { return TAnimClass; } }

        internal AnimationLibrary TLibrary { get; private set; }
        internal AnimationClass TCurrentClass { get { return (AnimationClass)TAB_ClassDetails.DataContext; } }
        internal AnimationSet TCurrentSet { get { return (AnimationSet)TAB_AnimSetDetails.DataContext; } }
        internal Animation TCurrentAnim { get { return TAB_AnimDetails.DataContext as Animation; } }
        internal AnimationClass TAnimClass
        {
            get
            {
                if (TAB_AnimDetails.DataContext is Animation)
                    return TCurrentAnim.SetOrNull.Class;
                else
                    return null;
            }
        }

        public BindingList<Vector2d> ResourceStacks { get { return TAnimClass.ResourceStacks; } }
        public BindingList<Vector4d> SolidBoundaries { get { return TAnimClass.SolidBoundaries; } }

        private System.Windows.Point m_SizeSelectStart;
        private System.Windows.Point m_SizeSelectEnd;

        public MainWindow()
        {
            InitializeComponent();

            TLibrary = AnimationLibrary.OpenOrCreate(@".\Library");
            DataContext = this;
        }

        private void BTN_CreateClass_Click(object sender, RoutedEventArgs e)
        {
            e.Handled = true;

            TLibrary.AddClass(EDIT_NewClassName.Text);
        }

        private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            object item;

            if (e != null)
            {
                e.Handled = true;

                item = e.NewValue;
            }
            else
                item = sender;

            if (item is AnimationClass)
            {
                AnimationClass animClass = item as AnimationClass;

                TAB_ClassDetails.DataContext = animClass;
                WIZARD_Details.SelectedItem = TAB_ClassDetails;
                GROUP_Details.Header = "Details For Animation Class \"" + animClass.Name + "\":";
                GROUP_AnimDetails.IsEnabled = false;
            }
            else if (item is AnimationSet)
            {
                AnimationSet animSet = item as AnimationSet;

                WIZARD_Details.SelectedItem = TAB_AnimSetDetails;
                GROUP_Details.Header = "Details For Animation Set \"" + animSet.Name + "\":";
                TAB_AnimSetDetails.DataContext = animSet;
                GROUP_AnimDetails.IsEnabled = false;
            }


            if (item is Animation)
            {
                Animation anim = item as Animation;

                WIZARD_Details.SelectedItem = TAB_AnimDetails;
                TAB_AnimDetails.DataContext = anim;
                GROUP_AnimDetails.DataContext = anim;
                GROUP_Details.Header = "Details For Animation Set \"" + anim.Name + "\":";
                GROUP_AnimDetails.Header = "Details For Animation \"" + anim.Name + "\":";
                GROUP_AnimDetails.IsEnabled = true;

                GROUP_ResourceStacks.DataContext = null;
                GROUP_ResourceStacks.DataContext = this;

                UpdateResourceStacks();

                // show animation
                AnimPlayer.Class = anim.SetOrNull.Class;
                AnimPlayer.Repeat = true;
                AnimPlayer.Play(anim.SetOrNull);
            }
            else
            {
                // save 

                // stop players
                if (AnimPlayer != null)
                    AnimPlayer.Stop();
            }
        }

        private void BTN_RemoveClass_Click(object sender, RoutedEventArgs e)
        {
            e.Handled = true;

            TLibrary.RemoveClass(TCurrentClass);
        }

        private void BTN_CreateSet_Click(object sender, RoutedEventArgs e)
        {
            e.Handled = true;

            TCurrentClass.AddAnimationSet(EDIT_NewSetName.Text);
        }

        private void BTN_ShowLibraryDetails_Click(object sender, RoutedEventArgs e)
        {
            e.Handled = true;

            WIZARD_Details.SelectedItem = TAB_LibDetails;
            GROUP_AnimDetails.IsEnabled = false;
        }

        private void BTN_CreateAnimation_Click(object sender, RoutedEventArgs e)
        {
            e.Handled = true;

            TCurrentSet.AddAnimation(EDIT_NewAnimName.Text);
        }

        private void BTN_Clear_Click(object sender, RoutedEventArgs e)
        {
            e.Handled = true;

            foreach (var frame in TCurrentAnim.Frames.ToArray())
            {
                TCurrentAnim.RemoveFrame(frame);
            }
        }

        private void BTN_MoveLeft_Click(object sender, RoutedEventArgs e)
        {
            e.Handled = true;

            AnimationFrame item = LIST_AnimFrames.SelectedItem as AnimationFrame;

            if (item != null)
                TCurrentAnim.MoveFrameLeft(item);

            LIST_AnimFrames.SelectedItem = item;
        }

        private void BTN_MoveRight_Click(object sender, RoutedEventArgs e)
        {
            e.Handled = true;

            AnimationFrame item = LIST_AnimFrames.SelectedItem as AnimationFrame;

            if (item != null)
                TCurrentAnim.MoveFrameRight(item);

            LIST_AnimFrames.SelectedItem = item;
        }

        private void BTN_StoreAtlas_Click(object sender, RoutedEventArgs e)
        {
            e.Handled = true;

            Int32 fullWidth_Hor = 0, fullHeight_Hor = 0, fullWidth_Vert = 0, fullHeight_Vert = 0;

            foreach (AnimationFrame frame in TCurrentAnim.Frames)
            {
                fullHeight_Hor = Math.Max(fullHeight_Hor, frame.Height);
                fullWidth_Vert = Math.Max(fullWidth_Vert, frame.Width);

                fullWidth_Hor += frame.Width + 10;
                fullHeight_Vert += frame.Height + 10;
            }

            Bitmap target;

            //if ((fullWidth_Hor * fullHeight_Hor) < (fullHeight_Vert * fullWidth_Vert))
            {
                // align horizontal
                target = new Bitmap(fullWidth_Hor, fullHeight_Hor, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                Graphics gTarget = Graphics.FromImage(target);

                using (gTarget)
                {
                    int offset = 0;

                    foreach (AnimationFrame frame in TCurrentAnim.Frames)
                    {
                        gTarget.DrawImageUnscaled(Bitmap.FromStream(new MemoryStream(frame.ToArray())), offset, 0);

                        offset += frame.Width + 10;
                    }
                }
            }
            //else
            {
                // align vertical
            }

            System.IO.File.Delete(".\\FrameAtlas.png");
            target.Save(".\\FrameAtlas.png", ImageFormat.Png);
        }

        private void BTN_LoadAtlas_Click(object sender, RoutedEventArgs e)
        {
            e.Handled = true;

            Int32 fullWidth_Hor = 0, fullHeight_Hor = 0, fullWidth_Vert = 0, fullHeight_Vert = 0;

            foreach (AnimationFrame frame in TCurrentAnim.Frames)
            {
                fullHeight_Hor = Math.Max(fullHeight_Hor, frame.Height);
                fullWidth_Vert = Math.Max(fullWidth_Vert, frame.Width);

                fullWidth_Hor += frame.Width + 10;
                fullHeight_Vert += frame.Height + 10;
            }

            if (!File.Exists(".\\FrameAtlas.png"))
            {
                MessageBox.Show("You have to store an atlas before loading it.");

                return;
            }

            Bitmap source = (Bitmap)Bitmap.FromFile(".\\FrameAtlas.png");

            using (source)
            {
                if ((fullWidth_Hor != source.Width) || (fullHeight_Hor != source.Height))
                {
                    MessageBox.Show("Stored atlas does not match current frame alignment!");

                    return;
                }

                //if ((fullWidth_Hor * fullHeight_Hor) < (fullHeight_Vert * fullWidth_Vert))
                {
                    // horizontal alignment
                    int offset = 0;
                    MemoryStream targetBytes = new MemoryStream();

                    foreach (AnimationFrame frame in TCurrentAnim.Frames)
                    {
                        Bitmap target = new Bitmap(frame.Width, frame.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                        Graphics gTarget = Graphics.FromImage(target);

                        targetBytes.SetLength(0);

                        using (target)
                        {
                            using (gTarget)
                            {
                                gTarget.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;

                                gTarget.DrawImage(
                                    source,
                                    new System.Drawing.Rectangle(0, 0, frame.Width, frame.Height),
                                    new System.Drawing.Rectangle(offset, 0, frame.Width, frame.Height),
                                    GraphicsUnit.Pixel);
                            }

                            target.Save(targetBytes, ImageFormat.Png);

                            frame.SetBitmap(targetBytes.ToArray());
                        }

                        offset += frame.Width + 10;
                    }
                }
                //else
                {
                    // align vertical
                }
            }

            GROUP_AnimDetails.DataContext = new Object();
            GROUP_AnimDetails.DataContext = TCurrentAnim;
        }

        static MainWindow()
        {
            Bitmap empty = new Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

            empty.SetPixel(0, 0, System.Drawing.Color.FromArgb(0, 0, 0, 0));

            MemoryStream stream = new MemoryStream();

            empty.Save(stream, ImageFormat.Png);

            EmptyBitmapBytes = stream.ToArray();
        }

        private static readonly byte[] EmptyBitmapBytes;

        private unsafe void BTN_MinimizeBounds_Click(object sender, RoutedEventArgs e)
        {
            e.Handled = true;

            MemoryStream targetBytes = new MemoryStream();

            int globalLeft = TCurrentAnim.Width, globalTop = TCurrentAnim.Height;

            foreach (AnimationFrame frame in TCurrentAnim.Frames)
            {
                globalLeft = Math.Min(globalLeft, frame.OffsetX);
                globalTop = Math.Min(globalTop, frame.OffsetY);
            }

            foreach (AnimationFrame frame in TCurrentAnim.Frames)
            {
                Bitmap source = (Bitmap)Bitmap.FromStream(new MemoryStream(frame.ToArray()));
                ImagePixelLock locked = new ImagePixelLock(source, false);
                Int32 left = frame.Width, top = frame.Height, bottom = 0, right = 0;

                targetBytes.SetLength(0);

                using (locked)
                {
                    // compute smallest bounds of extracted animation frame
                    int* destPtr = locked.Pixels;

                    for (int y = 0; y < frame.Height; y++)
                    {
                        for (int x = 0; x < frame.Width; x++)
                        {
                            int pixel = *(destPtr++);

                            if (pixel == 0)
                                continue;

                            if (x < left)
                                left = x;

                            if (y < top)
                                top = y;

                            if (y > bottom)
                                bottom = y;

                            if (x > right)
                                right = x;
                        }
                    }
                }

                int newWidth = right - left, newHeight = bottom - top;

                if ((newWidth != frame.Width) || (newHeight != frame.Height))
                {
                    if ((newWidth > 0) && (newHeight > 0))
                    {
                        frame.OffsetX += left - globalLeft;
                        frame.OffsetY += top - globalTop;
                        frame.Height = newHeight;
                        frame.Width = newWidth;

                        // create new bitmap
                        Bitmap target = new Bitmap(frame.Width, frame.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                        Graphics gTarget = Graphics.FromImage(target);

                        using (gTarget)
                        {
                            gTarget.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;

                            gTarget.DrawImage(
                                source,
                                new System.Drawing.Rectangle(0, 0, frame.Width, frame.Height),
                                new System.Drawing.Rectangle(left, top, frame.Width, frame.Height),
                                GraphicsUnit.Pixel);
                        }

                        target.Save(targetBytes, ImageFormat.Png);

                        frame.SetBitmap(targetBytes.ToArray());
                    }
                    else
                    {
                        // normalize empty frames
                        frame.SetBitmap(EmptyBitmapBytes);
                        frame.Width = 1;
                        frame.Height = 1;
                    }
                }
            }

            GROUP_AnimDetails.DataContext = new Object();
            GROUP_AnimDetails.DataContext = TCurrentAnim;
        }

        private void BTN_RemoveDups_Click(object sender, RoutedEventArgs e)
        {
            e.Handled = true;

            UniqueMap<Int64, Object> dups = new UniqueMap<long, object>();
            List<AnimationFrame> removals = new List<AnimationFrame>();

            foreach (AnimationFrame frame in TCurrentAnim.Frames)
            {
                if (dups.ContainsKey(frame.Checksum))
                    removals.Add(frame);
                else
                    dups.Add(frame.Checksum, null);
            }

            foreach (var frame in removals)
            {
                TCurrentAnim.RemoveFrame(frame);
            }

            GROUP_AnimDetails.DataContext = new Object();
            GROUP_AnimDetails.DataContext = TCurrentAnim;
        }

        private void BTN_RemoveFrame_Click(object sender, RoutedEventArgs e)
        {
            if (TCurrentAnim.FrozenFrame != null)
                TCurrentAnim.RemoveFrame(TCurrentAnim.FrozenFrame); // current selection is mapped to FrozenFrame
        }

        private void BTN_RemoveAnim_Click(object sender, RoutedEventArgs e)
        {
            if (TCurrentAnim.SetOrNull != null)
                TCurrentAnim.SetOrNull.RemoveAnimation(TCurrentAnim);
        }

        private void BTN_RenameAnim_Click(object sender, RoutedEventArgs e)
        {
            TCurrentAnim.SetOrNull.Rename(TCurrentAnim, EDIT_RenameAnim.Text);
        }

        private void BTN_RemoveSet_Click(object sender, RoutedEventArgs e)
        {
            TCurrentSet.Class.RemoveAnimationSet(TCurrentSet);
        }

        private void BTN_RenameSet_Click(object sender, RoutedEventArgs e)
        {
            TCurrentSet.Class.Rename(TCurrentSet, EDIT_NewAnimName.Text);
        }

        private void BTN_ClassRename_Click(object sender, RoutedEventArgs e)
        {
            TCurrentClass.Library.Rename(TCurrentClass, EDIT_NewSetName.Text);
        }

        private void BTN_AddAudio_Click(object sender, RoutedEventArgs e)
        {
            TLibrary.AddAudio(EDIT_AudioName.Text, File.ReadAllBytes(EDIT_AudioPath.Text));
        }

        private void BTN_RemoveAudio_Click(object sender, RoutedEventArgs e)
        {
            AudioObject audio = LIST_AudioObjects.SelectedItem as AudioObject;

            if (audio != null)
                TLibrary.RemoveAudio(audio);
        }

        private void BTN_Play_Click(object sender, RoutedEventArgs e)
        {
            AudioObject audio = LIST_AudioObjects.SelectedItem as AudioObject;

            if (audio != null)
                audio.CreatePlayer(null).Play(0);
        }

        private void PANEL_AnimPreview_MouseDown(object sender, MouseButtonEventArgs e)
        {
            PANEL_StackPlaneConfig.CaptureMouse();

            m_SizeSelectStart = e.GetPosition(PANEL_StackPlaneConfig);

            e.Handled = true;
        }

        private void PANEL_AnimPreview_MouseMove(object sender, MouseEventArgs e)
        {
            if (!PANEL_StackPlaneConfig.IsMouseCaptured)
                return;

            m_SizeSelectEnd = e.GetPosition(PANEL_StackPlaneConfig);

            if (m_SizeSelectEnd.X < m_SizeSelectStart.X)
                m_SizeSelectEnd.X = m_SizeSelectStart.X;

            if (m_SizeSelectEnd.Y < m_SizeSelectStart.Y)
                m_SizeSelectEnd.Y = m_SizeSelectStart.Y;

            UpdateSizeFrame();

            e.Handled = true;
        }

        private void UpdateSizeFrame()
        {
            PANEL_SizeOverlay.Margin = new Thickness(m_SizeSelectStart.X, m_SizeSelectStart.Y, 0, 0);
            PANEL_SizeOverlay.Width = m_SizeSelectEnd.X - m_SizeSelectStart.X;
            PANEL_SizeOverlay.Height = m_SizeSelectEnd.Y - m_SizeSelectStart.Y;
        }

        private void PANEL_AnimPreview_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (PANEL_StackPlaneConfig.IsMouseCaptured)
            {
                PANEL_StackPlaneConfig.ReleaseMouseCapture();

                TAnimClass.SolidBoundaries.Add(new Vector4d(m_SizeSelectStart.X, m_SizeSelectStart.Y, m_SizeSelectEnd.X, m_SizeSelectEnd.Y));
                UpdateResourceStacks();
            }

            e.Handled = true;
        }

        private void PANEL_AnimPreview_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
        {
            var tmp = e.GetPosition(PANEL_StackPlaneConfig);

            TAnimClass.ResourceStacks.Add(new Vector2d(tmp.X, tmp.Y));

            UpdateResourceStacks();

            e.Handled = true;
        }

        private void UpdateResourceStacks()
        {
            PANEL_ResourceOverlays.Children.Clear();

            foreach (var stack in ResourceStacks)
            {
                PANEL_ResourceOverlays.Children.Add(new Grid()
                {
                    VerticalAlignment = System.Windows.VerticalAlignment.Top,
                    HorizontalAlignment = System.Windows.HorizontalAlignment.Left,
                    Margin = new Thickness(stack.X - 10, stack.Y - 10, 0, 0),
                    Width = 20,
                    Height = 20,
                    Background = System.Windows.Media.Brushes.Blue,
                    Opacity = 0.5,
                });
            }

            PANEL_SizeOverlays.Children.Clear();

            foreach (var bound in SolidBoundaries)
            {
                PANEL_SizeOverlays.Children.Add(new Grid()
                {
                    VerticalAlignment = System.Windows.VerticalAlignment.Top,
                    HorizontalAlignment = System.Windows.HorizontalAlignment.Left,
                    Margin = new Thickness(bound.X, bound.Y, 0, 0),
                    Width = bound.Z,
                    Height = bound.W,
                    Background = System.Windows.Media.Brushes.Red,
                    Opacity = 0.5,
                });
            }
        }

        private void BTN_RemoveResource_Click(object sender, RoutedEventArgs e)
        {
            if (LIST_ResourceStacks.SelectedIndex >= 0)
            {
                ResourceStacks.RemoveAt(LIST_ResourceStacks.SelectedIndex);
            }

            UpdateResourceStacks();
        }

        private void BTN_ClearResources_Click(object sender, RoutedEventArgs e)
        {
            ResourceStacks.Clear();

            UpdateResourceStacks();
        }

        private void BTN_RemoveBound_Click(object sender, RoutedEventArgs e)
        {
            if (LIST_SolidBoundaries.SelectedIndex >= 0)
            {
                SolidBoundaries.RemoveAt(LIST_SolidBoundaries.SelectedIndex);
            }

            UpdateResourceStacks();
        }

        private void BTN_ClearBoundaries_Click(object sender, RoutedEventArgs e)
        {
            SolidBoundaries.Clear();

            UpdateResourceStacks();
        }

        private void Window_Closing(object sender, CancelEventArgs e)
        {
            AnimPlayer.Detach();
            //AnimSetPlayer.Detach();

            TLibrary.Save();
        }
    }
}
